using SharpDX;
using System;
using System.Globalization;
using System.IO;
using System.Xml;

namespace Engine.Graphics
{
    public struct MappingsWR2
    {
        #region Public Fields

        public Vector4 mappings1;
        public Vector4 mappings2;

        #endregion Public Fields

        #region Private Fields

        private uint crc;
        private float rotationX, rotationY, rotationZ, scaleX, scaleY;

        #endregion Private Fields

        #region Public Constructors

        public MappingsWR2(Vector4 mappings1, Vector4 mappings2)
        {
            this.rotationX = 90; this.rotationY = 0; this.rotationZ = 0; this.scaleX = 100; this.scaleY = 100;
            this.mappings1 = mappings1;
            this.mappings2 = mappings2;
            this.crc = 0;
            UpdateValues(out this.rotationX, out this.rotationY, out this.rotationZ, out this.scaleX, out this.scaleY);
            UpdateCRC();
        }

        public MappingsWR2(float rotationX, float rotationY, float rotationZ, float scaleX, float scaleY)
        {
            this.rotationX = rotationX;
            this.rotationY = rotationY;
            this.rotationZ = rotationZ;
            this.scaleX = scaleX;
            this.scaleY = scaleY;

            this.mappings1 = Vector4.Zero;
            this.mappings2 = Vector4.Zero;
            this.crc = 0;

            UpdateVectors();
            UpdateCRC();
        }

        #endregion Public Constructors

        #region Public Properties

        public uint CRC
        {
            get
            {
                return (uint)this.crc;
            }
        }

        public float OffsetX
        {
            get
            {
                return this.mappings1.W;
            }
            set
            {
                this.mappings1.W = value;
                UpdateCRC();
            }
        }

        public float OffsetY
        {
            get
            {
                return this.mappings2.W;
            }
            set
            {
                this.mappings2.W = value;
                UpdateCRC();
            }
        }

        public float RotationX
        {
            get
            {
                return this.rotationX;
            }
            set
            {
                this.rotationX = value;
                UpdateVectors();
                UpdateCRC();
            }
        }

        public float RotationY
        {
            get
            {
                if (float.IsNaN(this.rotationY))
                    return 0.0f;
                return this.rotationY;
            }
            set
            {
                this.rotationY = value;
                UpdateVectors();
                UpdateCRC();
            }
        }

        public float RotationZ
        {
            get
            {
                if (float.IsNaN(this.rotationZ))
                    return 0.0f;
                return this.rotationZ;
            }
            set
            {
                this.rotationZ = value;
                UpdateVectors();
                UpdateCRC();
            }
        }

        public float ScaleX
        {
            get
            {
                if (float.IsNaN(this.scaleX))
                    return 0;
                return this.scaleX;
            }
            set
            {
                this.scaleX = value;
                UpdateVectors();
                UpdateCRC();
            }
        }

        public float ScaleY
        {
            get
            {
                if (float.IsNaN(this.scaleY))
                    return 0;
                return this.scaleY;
            }
            set
            {
                this.scaleY = value;
                UpdateVectors();
                UpdateCRC();
            }
        }

        #endregion Public Properties

        #region Public Methods

        public static MappingsWR2 FromStream(BinaryReader br)
        {
            //#if DEBUG
            //System.Diagnostics.Debugger.Log(1, "1", "Surface Matrix Start at:  " + br.BaseStream.Position.ToString() + "\n");
            // Debug.WriteLine(string.Format("Surface Matrix Start at: {0} " , br.BaseStream.Position.ToString()));
            //#endif
            Vector4 mapping1 = new Vector4(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
            Vector4 mapping2 = new Vector4(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
            return new MappingsWR2(mapping1, mapping2);
        }

        #endregion Public Methods

        #region Internal Methods

        internal static MappingsWR2 FromXmlReader(XmlReader reader)
        {
            MappingsWR2 mappings = new MappingsWR2();

            if (reader.NodeType == XmlNodeType.Element
                    && reader.Name == "MaterialMapping")
            {
                mappings.RotationX = Convert.ToSingle(reader.GetAttribute("RotationX"), NumberFormatInfo.InvariantInfo);
                mappings.RotationY = Convert.ToSingle(reader.GetAttribute("RotationY"), NumberFormatInfo.InvariantInfo);
                mappings.RotationZ = Convert.ToSingle(reader.GetAttribute("RotationZ"), NumberFormatInfo.InvariantInfo);

                mappings.ScaleX = Convert.ToSingle(reader.GetAttribute("ScaleX"), NumberFormatInfo.InvariantInfo);
                mappings.ScaleY = Convert.ToSingle(reader.GetAttribute("ScaleY"), NumberFormatInfo.InvariantInfo);

                mappings.OffsetX = Convert.ToSingle(reader.GetAttribute("OffsetX"), NumberFormatInfo.InvariantInfo);
                mappings.OffsetY = Convert.ToSingle(reader.GetAttribute("OffsetY"), NumberFormatInfo.InvariantInfo);

                mappings.UpdateVectors();
                mappings.UpdateCRC();
            }

            return mappings;
        }

        internal void SaveToStream(BinaryWriter bw)
        {
            Write(bw);
        }

        internal void ToXmlStream(XmlWriter writer)
        {
            writer.WriteComment("Projection Matrix for Materials");

            writer.WriteStartElement("MaterialMapping");

            //writer.WriteAttributeString("mappings1X", Convert.ToString(this.mappings1.X, NumberFormatInfo.InvariantInfo));
            //writer.WriteAttributeString("mappings1Y", Convert.ToString(this.mappings1.Y, NumberFormatInfo.InvariantInfo));
            //writer.WriteAttributeString("mappings1Z", Convert.ToString(this.mappings1.Z, NumberFormatInfo.InvariantInfo));
            //writer.WriteAttributeString("mappings1W", Convert.ToString(this.mappings1.W, NumberFormatInfo.InvariantInfo));

            //writer.WriteAttributeString("mappings2X", Convert.ToString(this.mappings2.X, NumberFormatInfo.InvariantInfo));
            //writer.WriteAttributeString("mappings2Y", Convert.ToString(this.mappings2.Y, NumberFormatInfo.InvariantInfo));
            //writer.WriteAttributeString("mappings2Z", Convert.ToString(this.mappings2.Z, NumberFormatInfo.InvariantInfo));
            //writer.WriteAttributeString("mappings2W", Convert.ToString(this.mappings2.W, NumberFormatInfo.InvariantInfo));

            writer.WriteAttributeString("RotationX", Convert.ToString(this.rotationX, NumberFormatInfo.InvariantInfo));
            writer.WriteAttributeString("RotationY", Convert.ToString(this.rotationY, NumberFormatInfo.InvariantInfo));
            writer.WriteAttributeString("RotationZ", Convert.ToString(this.rotationZ, NumberFormatInfo.InvariantInfo));

            writer.WriteAttributeString("ScaleX", Convert.ToString(this.ScaleX, NumberFormatInfo.InvariantInfo));
            writer.WriteAttributeString("ScaleY", Convert.ToString(this.ScaleY, NumberFormatInfo.InvariantInfo));

            writer.WriteAttributeString("OffsetX", Convert.ToString(this.OffsetX, NumberFormatInfo.InvariantInfo));
            writer.WriteAttributeString("OffsetY", Convert.ToString(this.OffsetY, NumberFormatInfo.InvariantInfo));

            writer.WriteEndElement();
        }

        internal void Write(BinaryWriter bw)
        {
            bw.Write(this.mappings1.X);
            bw.Write(this.mappings1.Y);
            bw.Write(this.mappings1.Z);
            bw.Write(this.mappings1.W);

            bw.Write(this.mappings2.X);
            bw.Write(this.mappings2.Y);
            bw.Write(this.mappings2.Z);
            bw.Write(this.mappings2.W);
        }

        #endregion Internal Methods

        #region Private Methods

        private void UpdateCRC()
        {
            AdlerChecksum adlerChecksum = new AdlerChecksum();

            byte[] data = new byte[8 * 4];
            BitConverter.GetBytes(this.mappings1.X).CopyTo(data, 0);
            BitConverter.GetBytes(this.mappings1.Y).CopyTo(data, 4);
            BitConverter.GetBytes(this.mappings1.Z).CopyTo(data, 8);
            BitConverter.GetBytes(this.mappings1.W).CopyTo(data, 12);

            BitConverter.GetBytes(this.mappings2.X).CopyTo(data, 16);
            BitConverter.GetBytes(this.mappings2.Y).CopyTo(data, 20);
            BitConverter.GetBytes(this.mappings2.Z).CopyTo(data, 24);
            BitConverter.GetBytes(this.mappings2.W).CopyTo(data, 28);

            adlerChecksum.MakeForBuff(data);
            this.crc = adlerChecksum.ChecksumValue;
        }

        private void UpdateValues(out float rotationX, out float rotationY, out float rotationZ, out float scaleX, out float scaleY)
        {
            float sx, sy, ax, ay, az;
            sx = this.mappings1.Length();
            sy = this.mappings2.Length();

            ay = (float)Math.Asin((double)(this.mappings1.Z / sx));

            if (Math.Abs(Math.Cos((double)ay)) > 0.01)//Y in 0..89 else X&Z combine in one
            {
                az = (float)Math.Atan2((double)-this.mappings1.Y, (double)-this.mappings1.X);

                if (Math.Abs(Math.Sin(ay)) < 0.01)//Y = 0
                    if (Math.Abs(Math.Sin(az)) < 0.01)//Z = 0
                        ax = (float)Math.Atan2(this.mappings2.Y, this.mappings2.Z);//+
                    //         | 1 0 0 |
                    //         | 0 B A |
                    else
                        ax = (float)Math.Atan2((double)this.mappings2.X / Math.Sin((double)az), (double)this.mappings1.Z);
                //         | E    -F   0 |
                //         | BF   BE   A |    B=BF/F
                else if (Math.Abs(Math.Sin(az)) < 0.01)//Z = 0
                    ax = (float)Math.Atan2(this.mappings2.Y, this.mappings2.Z / Math.Cos(ay));
                //        |  C     0    D  | A=AC/C=Matrix[k,2,3]/cos(ay);
                //        | -AD    B    AC |
                else
                    ax = (float)Math.Atan2((this.mappings2.X + this.mappings2.Z / Math.Cos(ay) * Math.Sin(ay) * Math.Cos(az)) / Math.Sin(az), this.mappings2.Z / Math.Cos(ay));
                //        |  CE      -CF       D  | A=AC/C=Matrix[k,2,3]/cos(ay);
                //        | -ADE+BF   ADF+BE   AC | B=(21+ADE)/F=A*sin(ay)*cos(az)/sin(az)
                //21=-ADE+BF B=(21+ADE)/F
            }
            else
            {
                ax = 0;
                az = (float)Math.Atan2(this.mappings2.Y, -this.mappings2.X);
            }

            rotationX = (float)Math.Round(ax * 180.0 / Math.PI);
            rotationY = (float)Math.Round(ay * 180.0 / Math.PI);
            rotationZ = (float)Math.Round(az * 180.0 / Math.PI);

            scaleX = 1.0f / sx;
            scaleY = 1.0f / sy;
        }

        private void UpdateVectors()
        {
            double ax = this.rotationX / 180.0 * Math.PI;
            double ay = this.rotationY / 180.0 * Math.PI;
            double az = this.rotationZ / 180.0 * Math.PI;
            double sX = 1.0 / this.scaleX;
            double sY = 1.0 / this.scaleY;

            double a = Math.Cos(ax); double b = Math.Sin(ax);
            double c = Math.Cos(ay); double d = Math.Sin(ay);
            double e = Math.Cos(az); double f = Math.Sin(az);

            this.mappings1.X = (float)(c * e * sX);
            this.mappings1.Y = (float)(-c * f * sX);
            this.mappings1.Z = (float)(d * sX);
            this.mappings2.X = (float)((-a * d * e + b * f) * sY);
            this.mappings2.Y = (float)((a * d * f + b * e) * sY);
            this.mappings2.Z = (float)(a * c * sY);
        }

        #endregion Private Methods
    }
}